/**
 * 
 */
package edu.hitsz.cs.nlp.evaluation;

import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Vector;

import edu.hitsz.cs.nlp.struct.ConllFile;

/**
 * @author tm
 * evaluation for sentence with single tag for each token, such as Part-of-Speech
 */
public class SequenceEvalSingle {
	public Vector<String> tags;
	Vector<Integer> goldTags;
	Vector<Integer> predictTags;
	Vector<Integer> trueTags;
	public Vector<Double> tagPrecision;
	public Vector<Double> tagRecall;
	public Vector<Double> tagFValue;
	int totalGoldTag;
	int totalPredictTag;
	int totalTrueTag;
	public double totalPrecision;
	public double totalRecall;
	public double totalFValue;
	int sentenceCorrectNum;
	int totalSentenceNum;
	double sentenceCorrectProb;
	
	public SequenceEvalSingle(){
		tags=new Vector<String>();
		goldTags=new Vector<Integer>();
		predictTags=new Vector<Integer>();
		trueTags=new Vector<Integer>();
		tagPrecision=new Vector<Double>();
		tagRecall=new Vector<Double>();
		tagFValue=new Vector<Double>();
		totalGoldTag=0;
		totalPredictTag=0;
		totalTrueTag=0;
		totalPrecision=0;
		totalRecall=0;
		totalFValue=0;
		sentenceCorrectNum=0;
		totalSentenceNum=0;
		sentenceCorrectProb=0;
	}
	
	/**
	 * computer the information in every sentence
	 * @param goldSeq Gold sequence
	 * @param predictSeq Predicted sequence
	 * @return
	 */
	public boolean compEvalSent(Vector<String> goldSeq, Vector<String> predictSeq){
		int seqLen=goldSeq.size();
		if(seqLen!=predictSeq.size()){
			System.out.println("The sentence Length is different!");
			return false;
		}
		int senCorrectSignal=1;
		//for every token
		for(int i=0;i<seqLen;i++){
			String predictTag=predictSeq.get(i);
			String goldTag=goldSeq.get(i);
			//if the token is same for gold and predict tag
			if(predictTag.equals(goldTag)){
				if(tags.contains(predictTag)){
					int position=tags.indexOf(predictTag);
					int tmpNum=goldTags.get(position);
					goldTags.set(position,tmpNum+1);
					tmpNum=predictTags.get(position);
					predictTags.set(position,tmpNum+1);
					tmpNum=trueTags.get(position);
					trueTags.set(position,tmpNum+1);					
				}
				else{
					int j=0;
					for(;j<tags.size()&&predictTag.compareTo(tags.get(j))>0;j++);
					if(j<tags.size()){
						tags.add(j,predictTag);
						goldTags.add(j,1);
						predictTags.add(j,1);
						trueTags.add(j,1);
					}
					else{
						tags.add(predictTag);
						goldTags.add(1);
						predictTags.add(1);
						trueTags.add(1);
					}
				}
			}
			//if not
			else{
				senCorrectSignal=0;
				//for the tag in predict
				if(tags.contains(predictTag)){
					int position=tags.indexOf(predictTag);
					int tmpNum=predictTags.get(position);
					predictTags.set(position,tmpNum+1);					
				}
				else{
					int j=0;
					for(;j<tags.size()&&predictTag.compareTo(tags.get(j))>0;j++);
					if(j<tags.size()){
						tags.add(j,predictTag);
						goldTags.add(j,0);
						predictTags.add(j,1);
						trueTags.add(j,0);
					}
					else{
						tags.add(predictTag);
						goldTags.add(0);
						predictTags.add(1);
						trueTags.add(0);	
					}
				}
				//for the tag in gold
				if(tags.contains(goldTag)){
					int position=tags.indexOf(goldTag);
					int tmpNum=goldTags.get(position);
					goldTags.set(position,tmpNum+1);					
				}
				else{
					int j=0;
					for(;j<tags.size()&&goldTag.compareTo(tags.get(j))>0;j++);
					if(j<tags.size()){
						tags.add(j,goldTag);
						goldTags.add(j,1);
						predictTags.add(j,0);
						trueTags.add(j,0);
					}
					else{
						tags.add(goldTag);
						goldTags.add(1);
						predictTags.add(0);
						trueTags.add(0);
					}
				}
			}
		}
		if(senCorrectSignal==1)
			sentenceCorrectNum+=1;
		return true;
	}
	

	
	
	/**
	 * Computer the information in all sentences
	 * @param gold Gold sentences
	 * @param predict Predicted sentences
	 * @return
	 */
	public boolean compEvalAll(Vector<Vector<String>> gold, Vector<Vector<String>> predict){
		if(gold.size()==0||predict.size()==0)
		{
			System.out.println("The number of sentence is 0 in one of the gold and predict file. Please check");
			return false;
		}
		if(gold.size()!=predict.size()){
			System.out.println("The Number of Sentence is different. Please check");
			return false;
		}
		int senSize=gold.size();
		for(int i=0;i<senSize;i++)
			compEvalSent(gold.get(i),predict.get(i));

		return true;
	}
	
	/**
	 * compute the statistics of all data
	 */
	public void compEvalStat(){
		int tagSize=tags.size();
		if(tagSize==0){
			System.out.println("The Number of Tags is zero. Please check");
		}
		
		for(int i=0;i<tagSize;i++){
			//computer precision
			double precision=0;
			if(predictTags.get(i)==0){
				if(trueTags.get(i)>0){
					System.out.println("The number of predictTags is 0 and trueTags is larger than 0. Please check");
					System.exit(1);
				}
			}
			else
				precision=100*trueTags.get(i)/(double)predictTags.get(i);
			tagPrecision.add(precision);
			//computer recall
			double recall=0;
			if(goldTags.get(i)==0){
				if(trueTags.get(i)>0){
					System.out.println("The number of goldTags is 0 and trueTags is larger than 0. Please check");
					System.exit(1);
				}
			}
			else
				recall=100*trueTags.get(i)/(double)goldTags.get(i);
			tagRecall.add(recall);
			//computer f1-value
			double fvalue=0;
			if(Math.abs(precision+recall)>0.000001)
				fvalue=2*precision*recall/(precision+recall);
			tagFValue.add(fvalue);	
			totalGoldTag+=goldTags.get(i);
			totalPredictTag+=predictTags.get(i);
			totalTrueTag+=trueTags.get(i);
		}
		totalPrecision=0;
		if(totalPredictTag!=0)
			totalPrecision=100*totalTrueTag/(double)totalPredictTag;
		totalRecall=0;
		if(totalGoldTag!=0)
			totalRecall=100*totalTrueTag/(double)totalGoldTag;
		totalFValue=0;
		if(Math.abs(totalPrecision+totalRecall)>0.00001)
			totalFValue=2*totalPrecision*totalRecall/(totalPrecision+totalRecall);
		
	}	
		
	
	/**
	 * Computer the evaluation result from gold and predict file
	 * @param goldFileName Name of Golden File with the golden tags in last column of every line
	 * @param predictFileName Name of predicted File with the golden tags in last column of every line
	 */
	public void compEvalFromFile(String goldFileName, String predictFileName){
		ConllFile goldFile=new ConllFile();
		goldFile.readTrainFile(goldFileName, 0);
		ConllFile predictFile=new ConllFile();
		predictFile.readTrainFile(predictFileName, 0);
		if(goldFile.sentenceNumber!=predictFile.sentenceNumber){
			System.out.println("The sentence number is different. Please check");
		}
		for(int i=0;i<goldFile.sentenceNumber;i++){
			compEvalSent(goldFile.totalSentence.get(i).senResultSignal,predictFile.totalSentence.get(i).senResultSignal);
		}
		totalSentenceNum=goldFile.sentenceNumber;
		sentenceCorrectProb=sentenceCorrectNum/(double)totalSentenceNum;
		compEvalStat();		
	}
	
	
	/**
	 * Computer the evaluation result from gold and predict file
	 * @param goldFile Golden File with the golden tags in last column of every line
	 * @param predictFile predicted File with the golden tags in last column of every line
	 */
	public void compEvalFromFile(ConllFile goldFile, ConllFile predictFile){
		if(goldFile.sentenceNumber!=predictFile.sentenceNumber){
			System.out.println("The sentence number is different. Please check");
		}
		for(int i=0;i<goldFile.sentenceNumber;i++){
			compEvalSent(goldFile.totalSentence.get(i).senResultSignal,predictFile.totalSentence.get(i).senResultSignal);
		}
		totalSentenceNum=goldFile.sentenceNumber;
		sentenceCorrectProb=sentenceCorrectNum/(double)totalSentenceNum;
		compEvalStat();		
	}
	
	
	
	/**
	 * Store the evaluation file
	 * @param outPath outPath Path(Directory) of the file 
	 * @param outFileName
	 */
	public void storeEvalFile(String outPath, String outFileName){
		int tagSize=tags.size();
		if(tagSize==0){
			System.out.println("The Number of Tags is zero. Please check");
			System.exit(1);
		}
		String outFile=outPath+outFileName;
		try{
			FileWriter outFileWriter=new FileWriter(outFile);
			
			DecimalFormat df = new DecimalFormat("######0.00");  
			outFileWriter.write("processed "+totalGoldTag+" tags; found: "+totalPredictTag+" tags; Correct: "+totalTrueTag+"\n");
			outFileWriter.write("accuracy:\t\t\t\t\t\t\t\t\t\t"+"precision:\t"+df.format(totalPrecision)+";\t"+"recall:\t"+df.format(totalRecall)+";\t"+"FB1:\t"+df.format(totalFValue)+"\n");
			for(int i=0;i<tagSize;i++){			
				outFileWriter.write("\t\t"+tags.get(i)+":\t"+"gold:\t"+goldTags.get(i)+";\t"+"predict:\t"+predictTags.get(i)+";\t"+"correct:\t"+trueTags.get(i));
				outFileWriter.write("\tprecision:\t"+df.format(tagPrecision.get(i))+";\t"+"recall:\t"+df.format(tagRecall.get(i))+";\t"+"FB1:\t"+df.format(tagFValue.get(i))+"\n");
			}
			outFileWriter.write("\nprocessed "+totalSentenceNum+" sentences; found: "+sentenceCorrectNum+" sentences; Accuracy: "+sentenceCorrectProb+"\n");
			outFileWriter.close();
			System.out.println("\nStore Evaluation file done!");			
		}catch (IOException e){
			System.out.println("IOException: " + e);
		}
	}

	/**
	 * Display the evaluation result
	 */
	public void printEvalStat(){
		int tagSize=tags.size();
		if(tagSize==0){
			System.out.println("The Number of Tags is zero. Please check");
			System.exit(1);
		}
		DecimalFormat df = new DecimalFormat("######0.00");  
		System.out.println("processed "+totalGoldTag+" tags; found: "+totalPredictTag+" tags; Correct: "+totalTrueTag);
		System.out.println("accuracy:\t\t\t\t\t\t\t\t\t\t"+"precision:\t"+df.format(totalPrecision)+";\t"+"recall:\t"+df.format(totalRecall)+";\t"+"FB1:\t"+df.format(totalFValue));
		for(int i=0;i<tagSize;i++){			
			System.out.print("\t\t"+tags.get(i)+":\t"+"gold:\t"+goldTags.get(i)+";\t"+"predict:\t"+predictTags.get(i)+";\t"+"correct:\t"+trueTags.get(i));
			System.out.println("\tprecision:\t"+df.format(tagPrecision.get(i))+";\t"+"recall:\t"+df.format(tagRecall.get(i))+";\t"+"FB1:\t"+df.format(tagFValue.get(i)));
		}
		System.out.println("processed "+totalSentenceNum+" sentences; correct: "+sentenceCorrectNum+" sentences; Accuracy: "+sentenceCorrectProb);
	}
	
	/**
	 * return the f-value of the results
	 * @return
	 */
	public double getFValue(){
		return totalFValue;
	}
	
	/**
	 * statistic about the chunk agreement in those predicted sequences and golden sequence
	 * @param seqStat The vector to store the number of 7(Total, All Correct, Majority correct, Equal correct, Minority correct, all Wrong with vary; all wrong and agree) 
	 * @param predictSeqs Vector of sequences
	 * @param goldSeq
	 */
	public void compAgreement(Vector<String> goldSeq, Vector<Vector<String>> predictSeqs, Vector<Integer> seqStat){
		int seqNum=predictSeqs.size();
		if(seqStat.size()!=7){
			System.out.println("Number of seqStat is not equal to 7. Please check");
			System.exit(1);
		}
		else{
			//
			int tagSize=goldSeq.size();
			seqStat.set(0, seqStat.get(0)+tagSize);
			for(int i=0;i<tagSize;i++){				
				//gold vector to store one chunk
				String goldTag=(String) goldSeq.get(i);		
				HashMap<String,Integer> tagMap=new HashMap<String,Integer>();
				tagMap.clear();
				for(int j=0;j<seqNum;j++){	
					String predictTag=predictSeqs.get(j).get(i);
					if(tagMap.containsKey(predictTag)){
						tagMap.put(predictTag, tagMap.get(predictTag)+1);
					}
					else
						tagMap.put(predictTag, 1);
				}
				//find tag agreement
				if(tagMap.containsKey(goldTag)){
					int tmp=tagMap.get(goldTag);
					if(tmp==seqNum)
						seqStat.set(1, seqStat.get(1)+1);
					else if((double)tmp-(double)seqNum/2>0.001)
						seqStat.set(2,seqStat.get(2)+1);
					else if(tmp==seqNum/2){
						if(tagMap.size()>2)
							seqStat.set(2, seqStat.get(2)+1);
						else
							seqStat.set(3, seqStat.get(3)+1);
					}
					else if(tmp>=1)
						seqStat.set(4, seqStat.get(4)+1);						
				}
				else{
					if(tagMap.size()==1)
						seqStat.set(6, seqStat.get(6)+1);
					else
						seqStat.set(5, seqStat.get(5)+1);					
				}				
			}
		}				
	}
	
	/**
	 * Computer Tag Agreement from Files
	 * @param goldFileName
	 * @param predictFileNames
	 */
	public void compAgreementFromFile(String goldFileName, Vector<String> predictFileNames){
		//read golden and predicted tags from files
		ConllFile goldFile=new ConllFile();
		goldFile.readTrainFile(goldFileName, 0);
		int predictNum=predictFileNames.size();
		Vector<ConllFile> predictFiles=new Vector<ConllFile>();
		for(String predicteFileName : predictFileNames){
			ConllFile newFile=new ConllFile();
			newFile.readTrainFile(predicteFileName, 0);
			predictFiles.add(newFile);
		}
		//initial evaluation vector
		Vector<Integer> evalVec=new Vector<Integer>();
		for(int i=0;i<4;i++)
			evalVec.add(0);
		int senNumber=goldFile.sentenceNumber;
		if(senNumber!=predictFiles.get(0).sentenceNumber){
			System.out.println("The sentence number is different. Please check");
		}
		for(int i=0;i<senNumber;i++){
			Vector<Vector<String>> preVec=new Vector<Vector<String>>();
			for(int j=0;j<predictNum;j++)
				preVec.add(predictFiles.get(j).totalSentence.get(i).senResultSignal);
			compAgreement(goldFile.totalSentence.get(i).senResultSignal,preVec,evalVec);
		}
		System.out.println("done");	
	}
	
	
	
	static void test(){
		SequenceEvalSingle newEval=new SequenceEvalSingle();
		/*
		Vector s1=new Vector();
		Vector s2=new Vector();
		Vector<String> n1=new Vector<String>();
		Vector<String> n2=new Vector<String>();
		n1.add("VB");
		n1.add("POS");
		n1.add("NNP");
		n2.add("POS");
		n2.add("VB");
		n2.add("NNP");
		s1.add(n1);
		s2.add(n2);
		newEval.compAll(s1,s2);
		newEval.compStat();
		newEval.printStat();
		*/
		newEval.compEvalFromFile("/windows/F/experiments/joint/20wp", "20wpp");
		newEval.printEvalStat();
		newEval.storeEvalFile("/windows/F/experiments/joint/", "111");		
	}
	
	static void test2(){
		SequenceEvalSingle newEval=new SequenceEvalSingle();
		String path="/windows/F/experiments/crf/";
		String goldFileName="1";
		Vector<String> predictNames=new Vector<String>();
		predictNames.add("2");predictNames.add("3");predictNames.add("4");predictNames.add("5");
		newEval.compAgreementFromFile(goldFileName,predictNames);			
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		test2();
		
	}

}
